{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "# Simple Reinforcement Learning in Tensorflow Part 1.5: \n",
    "## The Contextual Bandits\n",
    "This tutorial contains a simple example of how to build a policy-gradient based agent that can solve the contextual bandit problem. For more information, see this [Medium post](https://medium.com/p/bff01d1aad9c).\n",
    "\n",
    "For more Reinforcement Learning algorithms, including DQN and Model-based learning in Tensorflow, see my Github repo, [DeepRL-Agents](https://github.com/awjuliani/DeepRL-Agents). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import tensorflow.contrib.slim as slim\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "### The Contextual Bandits\n",
    "Here we define our contextual bandits. In this example, we are using three four-armed bandit. What this means is that each bandit has four arms that can be pulled. Each bandit has different success probabilities for each arm, and as such requires different actions to obtain the best result. The pullBandit function generates a random number from a normal distribution with a mean of 0. The lower the bandit number, the more likely a positive reward will be returned. We want our agent to learn to always choose the bandit-arm that will most often give a positive reward, depending on the Bandit presented."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "class contextual_bandit():\n",
    "    def __init__(self):\n",
    "        self.state = 0\n",
    "        #List out our bandits. Currently arms 4, 2, and 1 (respectively) are the most optimal.\n",
    "        self.bandits = np.array([[0.2,0,-0.0,-5],[0.1,-5,1,0.25],[-5,5,5,5]])\n",
    "        self.num_bandits = self.bandits.shape[0]\n",
    "        self.num_actions = self.bandits.shape[1]\n",
    "        \n",
    "    def getBandit(self):\n",
    "        self.state = np.random.randint(0,len(self.bandits)) #Returns a random state for each episode.\n",
    "        return self.state\n",
    "        \n",
    "    def pullArm(self,action):\n",
    "        #Get a random number.\n",
    "        bandit = self.bandits[self.state,action]\n",
    "        result = np.random.randn(1)\n",
    "        if result > bandit:\n",
    "            #return a positive reward.\n",
    "            return 1\n",
    "        else:\n",
    "            #return a negative reward.\n",
    "            return -1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "### The Policy-Based Agent\n",
    "The code below established our simple neural agent. It takes as input the current state, and returns an action. This allows the agent to take actions which are conditioned on the state of the environment, a critical step toward being able to solve full RL problems. The agent uses a single set of weights, within which each value is an estimate of the value of the return from choosing a particular arm given a bandit. We use a policy gradient method to update the agent by moving the value for the selected action toward the recieved reward."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "class agent():\n",
    "    def __init__(self, lr, s_size,a_size):\n",
    "        #These lines established the feed-forward part of the network. The agent takes a state and produces an action.\n",
    "        self.state_in= tf.placeholder(shape=[1],dtype=tf.int32)\n",
    "        state_in_OH = slim.one_hot_encoding(self.state_in,s_size)\n",
    "        output = slim.fully_connected(state_in_OH,a_size,\\\n",
    "            biases_initializer=None,activation_fn=tf.nn.sigmoid,weights_initializer=tf.ones_initializer())\n",
    "        self.output = tf.reshape(output,[-1])\n",
    "        self.chosen_action = tf.argmax(self.output,0)\n",
    "\n",
    "        #The next six lines establish the training proceedure. We feed the reward and chosen action into the network\n",
    "        #to compute the loss, and use it to update the network.\n",
    "        self.reward_holder = tf.placeholder(shape=[1],dtype=tf.float32)\n",
    "        self.action_holder = tf.placeholder(shape=[1],dtype=tf.int32)\n",
    "        self.responsible_weight = tf.slice(self.output,self.action_holder,[1])\n",
    "        self.loss = -(tf.log(self.responsible_weight)*self.reward_holder)\n",
    "        optimizer = tf.train.GradientDescentOptimizer(learning_rate=lr)\n",
    "        self.update = optimizer.minimize(self.loss)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "### Training the Agent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "We will train our agent by getting a state from the environment, take an action, and recieve a reward. Using these three things, we can know how to properly update our network in order to more often choose actions given states that will yield the highest rewards over time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mean reward for each of the 3 bandits: [ 0.    0.    0.25]\n",
      "Mean reward for each of the 3 bandits: [ 26.5   38.25  35.5 ]\n",
      "Mean reward for each of the 3 bandits: [ 68.25  75.25  70.75]\n",
      "Mean reward for each of the 3 bandits: [ 104.25  112.25  107.25]\n",
      "Mean reward for each of the 3 bandits: [ 142.5   147.5   145.75]\n",
      "Mean reward for each of the 3 bandits: [ 181.5   185.75  178.5 ]\n",
      "Mean reward for each of the 3 bandits: [ 215.5   223.75  220.  ]\n",
      "Mean reward for each of the 3 bandits: [ 256.5   260.75  249.5 ]\n",
      "Mean reward for each of the 3 bandits: [ 293.5   300.25  287.5 ]\n",
      "Mean reward for each of the 3 bandits: [ 330.25  341.    323.5 ]\n",
      "Mean reward for each of the 3 bandits: [ 368.75  377.    359.  ]\n",
      "Mean reward for each of the 3 bandits: [ 411.5   408.75  395.  ]\n",
      "Mean reward for each of the 3 bandits: [ 447.    447.    429.75]\n",
      "Mean reward for each of the 3 bandits: [ 484.    482.75  466.  ]\n",
      "Mean reward for each of the 3 bandits: [ 522.5   520.    504.75]\n",
      "Mean reward for each of the 3 bandits: [ 560.25  557.75  538.25]\n",
      "Mean reward for each of the 3 bandits: [ 597.75  596.25  574.75]\n",
      "Mean reward for each of the 3 bandits: [ 636.5   630.5   611.25]\n",
      "Mean reward for each of the 3 bandits: [ 675.25  670.    644.5 ]\n",
      "Mean reward for each of the 3 bandits: [ 710.5   706.5   682.75]\n",
      "The agent thinks action 4 for bandit 1 is the most promising....\n",
      "...and it was right!\n",
      "The agent thinks action 2 for bandit 2 is the most promising....\n",
      "...and it was right!\n",
      "The agent thinks action 1 for bandit 3 is the most promising....\n",
      "...and it was right!\n"
     ]
    }
   ],
   "source": [
    "tf.reset_default_graph() #Clear the Tensorflow graph.\n",
    "\n",
    "cBandit = contextual_bandit() #Load the bandits.\n",
    "myAgent = agent(lr=0.001,s_size=cBandit.num_bandits,a_size=cBandit.num_actions) #Load the agent.\n",
    "weights = tf.trainable_variables()[0] #The weights we will evaluate to look into the network.\n",
    "\n",
    "total_episodes = 10000 #Set total number of episodes to train agent on.\n",
    "total_reward = np.zeros([cBandit.num_bandits,cBandit.num_actions]) #Set scoreboard for bandits to 0.\n",
    "e = 0.1 #Set the chance of taking a random action.\n",
    "\n",
    "init = tf.global_variables_initializer()\n",
    "\n",
    "# Launch the tensorflow graph\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "    i = 0\n",
    "    while i < total_episodes:\n",
    "        s = cBandit.getBandit() #Get a state from the environment.\n",
    "        \n",
    "        #Choose either a random action or one from our network.\n",
    "        if np.random.rand(1) < e:\n",
    "            action = np.random.randint(cBandit.num_actions)\n",
    "        else:\n",
    "            action = sess.run(myAgent.chosen_action,feed_dict={myAgent.state_in:[s]})\n",
    "        \n",
    "        reward = cBandit.pullArm(action) #Get our reward for taking an action given a bandit.\n",
    "        \n",
    "        #Update the network.\n",
    "        feed_dict={myAgent.reward_holder:[reward],myAgent.action_holder:[action],myAgent.state_in:[s]}\n",
    "        _,ww = sess.run([myAgent.update,weights], feed_dict=feed_dict)\n",
    "        \n",
    "        #Update our running tally of scores.\n",
    "        total_reward[s,action] += reward\n",
    "        if i % 500 == 0:\n",
    "            print(\"Mean reward for each of the \" + str(cBandit.num_bandits) + \" bandits: \" + str(np.mean(total_reward,axis=1)))\n",
    "        i+=1\n",
    "for a in range(cBandit.num_bandits):\n",
    "    print(\"The agent thinks action \" + str(np.argmax(ww[a])+1) + \" for bandit \" + str(a+1) + \" is the most promising....\")\n",
    "    if np.argmax(ww[a]) == np.argmin(cBandit.bandits[a]):\n",
    "        print(\"...and it was right!\")\n",
    "    else:\n",
    "        print(\"...and it was wrong!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
